#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <memory>
#include "thread.hpp"
#include "log.hpp"
#define SIZE 1024



//////////////////////////////////////////////////////////////////
//////演示三：简易版聊天系统（多线程模式）

//服务器IP、Port：
//由于客户端我们没有封装，而这两个参数在线程中需要，一种方法是写类进行处理，另一种方法是定义成全局函数
//不存在线程安全问题，因为我们只是使用它，不是对它进行修改。(将sock当作一个文件，UDP是全双工的，可以同时进行收发而不受干扰)
in_addr_t server_ip;
uint16_t server_port;

// 使用手册：当命令行输入不正确时，可提示正确使用信息
void Usage(std::string proc)
{
    std::cout << "\nUsage:" << proc << " server_ip server_port\n"
              << std::endl;
}

// 用于向服务端发送数据消息
void *udpSend(void *pdata)
{
    // 1、通信前的准备：
    // a、获取args参数：
    int sock = *(int*)(((ThreadData *)pdata)->_args);


    // b、获取服务端的IP、端口号
    struct sockaddr_in serveraddr;
    bzero(&serveraddr, sizeof serveraddr);
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = server_port; // 这里已经在main函数中做了转换
    serveraddr.sin_addr.s_addr = server_ip;

    // 2、客户端从显示器中输入数据，将其发送给服务端：sendto（循环式发送）
    while (true)
    {
        string message;
        //std::cout << "client-请输入# ";
        std::cerr << "client-请输入# ";

        std::getline(std::cin, message);
        if (message == "quit") // string类中有operator==,建议访问官网查阅
            break;
        // 当client首次发送消息给服务器的时候，OS会自动给client bindIP和PORT.
        sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr *)&serveraddr, sizeof serveraddr);
    }
}

// 用于从服务端接收数据信息
void* udpRecv(void* pdata)
{

    // 1、通信前的准备：
    // a、获取args参数
    int sock = *(int*)(((ThreadData*)pdata)->_args);

    // 2、recvfrom循环式接收服务器发送过来的数据
    while(true)
    {
                   
        char client_buffer[SIZE]; // 用于存储数据
        struct sockaddr_in temp;  // 临时变量：用于recvfrom中参数(为保证该函数成功使用)
        memset(&temp, 0, sizeof temp);
        socklen_t len = sizeof temp;
        ssize_t s = recvfrom(sock, client_buffer, sizeof client_buffer, 0, (struct sockaddr *)&temp, &len);
        if (s > 0) // 接收到实际数据
        {
            client_buffer[s] = 0;
            std::cout << client_buffer << std::endl;
        }
    }
}

// 启动方式：udp_client server_ip server_port
int main(int argc, char **argv)
{
    // 1、检测命令行参数是否输入正确
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    //获取服务器的IP、套接字，将其转换为网络传送需要的套接字
    server_ip = inet_addr(argv[1]);//点分十进制字符串风格--->网络字节序+4字节
    server_port = htons(atoi(argv[2]));//字符型--->网络字节序+整型

    // 2、创建套接字
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) // 创建失败
    {
        std::cerr << "socket error" << std::endl;
        exit(2);
    }
    logMessage(DEBUG, "socket succes. sock: %d", sock);


    // 3、创建两个线程，分别用于管理客户端的[发送消息]和[接收消息]
    std::unique_ptr<Thread> sender(new Thread(1,udpSend,(void*)&sock));
    std::unique_ptr<Thread> recever(new Thread(2,udpRecv,(void*)&sock));
    sender->start();
    recever->start();

    sender->join();
    recever->join();

    // 4、结束：关闭套接字
    close(sock);

    return 0;
}
//////////////////////////////////////////////////////////////////







////////////////////////////////////////////////////////////////
//////演示一、演示二相关代码：
namespace Udp_test1_2
{
    // 使用手册：当命令行输入不正确时，可提示正确使用信息
    void Usage(std::string proc)
    {
        std::cout << "\nUsage:" << proc << " server_ip server_port\n"
                  << std::endl;
    }

    // 启动方式：udp_client server_ip server_port
    int main(int argc, char **argv)
    {
        // 1、检测命令行参数是否输入正确
        if (argc != 3)
        {
            Usage(argv[0]);
            exit(1);
        }

        // 2、创建套接字
        int sock = socket(AF_INET, SOCK_DGRAM, 0);
        if (sock < 0) // 创建失败
        {
            std::cerr << "socket error" << std::endl;
            exit(2);
        }
        logMessage(DEBUG, "socket succes. sock: %d", sock);

        // 3、进行网络通信
        // 3.1、通信前的准备：获取服务端的IP、端口号
        struct sockaddr_in serveraddr;
        bzero(&serveraddr, sizeof serveraddr);
        serveraddr.sin_family = AF_INET;
        serveraddr.sin_port = htons(atoi(argv[2])); // 要向服务器发送数据，要先经过网络，故这里存在字节序的转换(使用与server同)
        serveraddr.sin_addr.s_addr = inet_addr(argv[1]);

        // 3.2、循环式向服务端发出请求并接收响应结果
        // PS:首次测试，演示字符型数据传递，echo服务器
        while (true)
        {
            // a、向服务器发送请求
            std::cout << "client-请输入# ";
            std::string message; // 将交付的信息数据
            std::getline(std::cin, message);
            if (message == "quit") // string类中有operator==,建议访问官网查阅
                break;

            sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr *)&serveraddr, sizeof serveraddr);

            // b、接收来自服务器的响应
            char client_buffer[SIZE]; // 用于存储数据
            struct sockaddr_in temp;  // 临时变量：用于recvfrom中参数(为保证该函数成功使用)
            memset(&temp, 0, sizeof temp);
            socklen_t len = sizeof temp;
            ssize_t s = recvfrom(sock, client_buffer, sizeof client_buffer, 0, (struct sockaddr *)&temp, &len);
            if (s > 0) // 接收到实际数据
            {
                client_buffer[s] = 0;
                // echo版服务器，相关后续处理：打印到显示器表示服务端有确切地将数据通过网络sendto到客户端
                std::cout << "server echo : " << client_buffer << std::endl;
            }
        }

        return 0;
    }
}
////////////////////////////////////////////////////////////////
